
//  Copyright (C) 2009-2011 Luca Piccioni
// 
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
// 
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
// 
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;

using Derm.Render;
using Derm.Types;

namespace Derm.Raster
{
	/// <summary>
	/// ImageCodec for DIB file format.
	/// </summary>
	internal class ImageLoaderDIB : ImageCodecPlugin
	{
		#region DIB File Format
		
		/// <summary>
		/// DIB file header.
		/// </summary>
		private struct DibFileHeader
		{
			/// <summary>
			/// Construct a DibFileHeader from a binary stream.
			/// </summary>
			/// <param name="br">
			/// A <see cref="BinaryReader"/> where data is obtained.
			/// </param>
			/// <remarks>
			/// The BinaryReader shall be positioned correctly on its base stream.
			/// </remarks>
			public DibFileHeader(BinaryReader br)
			{
				// Initialize structure
				mSignature = 0;
				mSize = 0;
				mReserved = 0;
				mBitsOffset = 0;
				// Read structure from stream
				Read(br);
			}

			/// <summary>
			/// Construct a DibFileHeader from an Image.
			/// </summary>
			/// <param name="image">
			/// The <see cref="Image"/> represented by constructed DibFileHeader.
			/// </param>
			public DibFileHeader(Image image)
			{
				mSignature = 0x4d42;
				mSize = (uint)(image.Width*image.Height*3+DibFileHeader.Size+DibInfoHeader.Size);
				mReserved = 0;
				mBitsOffset = (uint)DibFileHeader.Size+40;
			}

			/// <summary>
			/// Read a DibFileHeader from stream.
			/// </summary>
			/// <param name="br">
			/// A <see cref="BinaryReader"/> where data is obtained.
			/// </param>
			public void Read(BinaryReader br)
			{
				mSignature = br.ReadUInt16();
				mSize = br.ReadUInt32();
				mReserved = br.ReadUInt32();
				mBitsOffset = br.ReadUInt32();
			}

			/// <summary>
			/// Write a DibFileHeader to stream.
			/// </summary>
			/// <param name="bw">
			/// A <see cref="BinaryWriter"/> where data is stored.
			/// </param>
			public void Write(BinaryWriter bw)
			{
				bw.Write(mSignature);
				bw.Write(mSize);
				bw.Write(mReserved);
				bw.Write(mBitsOffset);
			}
			
			/// <summary>
			/// DIB signature.
			/// </summary>
			public ushort mSignature;
			/// <summary>
			/// DIB file header size (in bytes).
			/// </summary>
			public uint mSize;
			/// <summary>
			/// Padding bytes.
			/// </summary>
			public uint mReserved;
			/// <summary>
			/// 
			/// </summary>
			public uint mBitsOffset;

			/// <summary>
			/// DibFileHeader standard size (in bytes).
			/// </summary>
			public static readonly int Size = 14;
		};
		
		/// <summary>
		/// Supported DIB compression.
		/// </summary>
		private enum DibCompression
		{
			None = 0,
			RLE8 = 1,
			RLE4 = 2,
			Bitfields = 3
		};
		
		/// <summary>
		/// DIB information header.
		/// </summary>
		private struct DibInfoHeader
		{
			/// <summary>
			/// Constructor a DibInfoHeader from a binary stream.
			/// </summary>
			/// <param name="br">
			/// A <see cref="BinaryReader"/> from where data is obtained.
			/// </param>
			/// <remarks>
			/// The BinaryReader shall be positioned correctly on its base stream.
			/// </remarks>
			public DibInfoHeader(BinaryReader br)
			{
				// Initialize structure
				mHeaderSize = 0; mWidth = 0; mHeight = 0;
				mPlanes = 0; mBitCount = 0;
				mCompression = 0; mSizeImage = 0;
				mPelsPerMeterX = 0; mPelsPerMeterY = 0;
				mClrUsed = 0; mClrImportant = 0;
				mRedMask = 0; mGreenMask = 0; mBlueMask = 0; mAlphaMask = 0;
				mCsType = 0;
				mEndpoints = new uint[9];
				for (int i = 0; i < 9; i++) mEndpoints[i] = 0;
				mGammaRed = 0; mGammaGreen = 0; 	mGammaBlue = 0;
				// Read structure from stream
				Read(br);
			}

			/// <summary>
			/// Construct a DibInfoHeader from an Image.
			/// </summary>
			/// <param name="image">
			/// The <see cref="Image"/> represented by constructed DibFileHeader.
			/// </param>
			public DibInfoHeader(Image image)
			{
				mHeaderSize = 40;
				mWidth = (int)image.Width;
				mHeight = (int)image.Height;
				mPlanes = 1;
				mBitCount = (ushort)Pixel.GetPixelFormat(image.PixelFormat).PixelBits;
				mCompression = (uint)DibCompression.None;
				mSizeImage = 0;
				mPelsPerMeterX = 0;
				mPelsPerMeterY = 0;
				mClrUsed = 0;
				mClrImportant = 0;
				mRedMask = 0;
				mGreenMask = 0;
				mBlueMask = 0;
				mAlphaMask = 0;
				mCsType = 0;
				mEndpoints = new uint[9];
				for (int i = 0; i < 9; i++)
					mEndpoints[i] = 0;
				mGammaRed = 0;
				mGammaGreen = 0;
				mGammaBlue = 0;
			}

			/// <summary>
			/// Read a DibInfoHeader from stream.
			/// </summary>
			/// <param name="br">
			/// A <see cref="BinaryReader"/> where data is obtained.
			/// </param>
			public void Read(BinaryReader br)
			{
				mHeaderSize = br.ReadUInt32();
				mWidth = br.ReadInt32();
				mHeight = br.ReadInt32();
				mPlanes = br.ReadUInt16();
				mBitCount = br.ReadUInt16();
				mCompression = br.ReadUInt32();
				mSizeImage = br.ReadUInt32();
				mPelsPerMeterX = br.ReadInt32();
				mPelsPerMeterY = br.ReadInt32();
				mClrUsed = br.ReadUInt32();
				mClrImportant = br.ReadUInt32();
				mRedMask = br.ReadUInt32();
				mGreenMask = br.ReadUInt32();
				mBlueMask = br.ReadUInt32();
				mAlphaMask = br.ReadUInt32();
				mCsType = br.ReadUInt32();
				for (int i = 0; i < 9; i++)
					mEndpoints[i] = br.ReadUInt32();
				mGammaRed = br.ReadUInt32();
				mGammaGreen = br.ReadUInt32();
				mGammaBlue = br.ReadUInt32();
			}

			/// <summary>
			/// Write a DibInfoHeader to stream.
			/// </summary>
			/// <param name="bw">
			/// A <see cref="BinaryWriter"/> where data is stored.
			/// </param>
			public void Write(BinaryWriter bw)
			{
				bw.Write(mHeaderSize);
				bw.Write(mWidth);
				bw.Write(mHeight);
				bw.Write(mPlanes);
				bw.Write(mBitCount);
				bw.Write(mCompression);
				bw.Write(mSizeImage);
				bw.Write(mPelsPerMeterX);
				bw.Write(mPelsPerMeterY);
				bw.Write(mClrUsed);
				bw.Write(mClrImportant);
			}
			
			/// <summary>
			/// Size of the DIB header.
			/// </summary>
			public uint mHeaderSize;
			/// <summary>
			/// DIB layer width.
			/// </summary>
			public int mWidth;
			/// <summary>
			/// DIB layer height.
			/// </summary>
			public int mHeight;
			/// <summary>
			/// DIB layer count.
			/// </summary>
			/// <remarks>
			/// It must be 1.
			/// </remarks>
			public ushort mPlanes;
			public ushort mBitCount;
			/// <summary>
			/// DIB data compression method.
			/// </summary>
			/// <remarks>
			/// Possible values are declared with Compression.
			/// </remarks>
			public uint mCompression;
			public uint mSizeImage;
			public int mPelsPerMeterX;
			public int mPelsPerMeterY;
			/// <summary>
			/// 
			/// </summary>
			public uint mClrUsed;
			public uint mClrImportant;
			public uint mRedMask;
			public uint mGreenMask;
			public uint mBlueMask;
			public uint mAlphaMask;
			public uint mCsType;
			/// <summary>
			/// 
			/// </summary>
			/// <remarks>
			/// See http://msdn2.microsoft.com/en-us/library/ms536569.aspx.
			/// </remarks>
			public uint[] mEndpoints; 
			public uint mGammaRed;
			public uint mGammaGreen;
			public uint mGammaBlue;

			/// <summary>
			/// DibFileHeader standard size (in bytes).
			/// </summary>
			public static readonly int Size = 98;
		};
		
		/// <summary>
		/// DIB pixel.
		/// </summary>
		struct DibBGRA
		{
			/// <summary>
			/// DibBGRA standard size (in bytes).
			/// </summary>
			public static readonly int Size = 4;
			
			/// <summary>
			/// DibBGRA constructor.
			/// </summary>
			/// <param name="br">
			/// A <see cref="BinaryReader"/>
			/// </param>
			public DibBGRA(BinaryReader br)
			{
				mBlue = br.ReadByte();
				mGreen = br.ReadByte();
				mRed = br.ReadByte();
				mAlpha = br.ReadByte();
			}
			public byte mBlue;
			public byte mGreen;
			public byte mRed;
			public byte mAlpha;
		};

		#endregion
		
		#region ImageCodecPlugin Implementation

		/// <summary>
		/// Plugin name.
		/// </summary>
		public override string Name { get { return ("DermDIB"); } }

		/// <summary>
		/// Determine whether this plugin is available for the current process.
		/// </summary>
		/// <returns>
		/// It returns a boolean value indicating whether the plugin is available for the current
		/// process.
		/// </returns>
		public override bool CheckAvailability()
		{
			return (true);
		}
		
		/// <summary>
		/// Get a list of image formats supported for reading.
		/// </summary>
		public override IEnumerable<ImageFormat> SupportedReadImageFormats
		{
			get {
				return (sSupportedReadFormats);
			}
		}

		/// <summary>
		/// Get a list of image formats supported for writing.
		/// </summary>
		public override IEnumerable<ImageFormat> SupportedWriteImageFormats
		{
			get {
				return (null);
			}
		}

		/// <summary>
		/// Currently supported image formats for reading.
		/// </summary>
		private static ImageFormat[] sSupportedReadFormats = new ImageFormat[] {
			ImageFormat.Bitmap
		};
		
		/// <summary>
		/// Query image file informations.
		/// </summary>
		/// <returns>
		/// A <see cref="ImageInfo"/>
		/// </returns>
		public override ImageInfo QueryImageInfo(Stream fs, ImageCodec.CodecCriteria criteria)
		{
			ImageInfo mInfo = new ImageInfo();
			
			return (mInfo);
		}
		
		/// <summary>
		/// Load image pixels from file.
		/// </summary>
		/// <param name="criteria">
		/// A <see cref="ImageCodec.CodecCriteria"/> specifying parameters for loading an image stream.
		/// </param>
		/// <returns>
		/// A <see cref="RasterImage"/>
		/// </returns>
		public override Image Load(Stream fs, ImageCodec.CodecCriteria criteria)
		{
			Image image = null;
			BinaryReader br = new BinaryReader(fs);
			DibFileHeader dibFileHeader = new DibFileHeader(br);
			DibInfoHeader dibInfoHeader = new DibInfoHeader(br);
			DibBGRA[] dibColorTable = null;
			uint dibColorTableSize;
			int dibLineWidth;
			byte[] dibLine;
			bool dibSkipColorTable = false;
			bool dibTopDown = dibInfoHeader.mHeight < 0;
			
			if (fs.CanSeek == false)
				throw new Exception("unable to load DIB file on unseekable stream");
			
			// Manage top-down DIB
			if (dibTopDown == true)
				dibInfoHeader.mHeight = -dibInfoHeader.mHeight;
		
			#region Read DIB Color Table

			// Read color table (if any)
			switch (dibInfoHeader.mBitCount) {
			case 1:
				dibColorTableSize = 2;
				image = new RasterImage<ColorRGBA32>((uint)dibInfoHeader.mWidth, (uint)dibInfoHeader.mHeight);
				break;
			case 4:
				dibColorTableSize = 16;
				image = new RasterImage<ColorRGBA32>((uint)dibInfoHeader.mWidth, (uint)dibInfoHeader.mHeight);
				break;
			case 8:
				dibColorTableSize = 256;
				image = new RasterImage<ColorRGBA32>((uint)dibInfoHeader.mWidth, (uint)dibInfoHeader.mHeight);
				break;
			case 16:
				dibColorTableSize = dibInfoHeader.mClrUsed;
				dibSkipColorTable = true;
				image = new RasterImage<ColorRGB16>((uint)dibInfoHeader.mWidth, (uint)dibInfoHeader.mHeight);
				break;
			case 24:
				dibColorTableSize = dibInfoHeader.mClrUsed;
				dibSkipColorTable = true;
				image = new RasterImage<ColorRGB24>((uint)dibInfoHeader.mWidth, (uint)dibInfoHeader.mHeight);
				break;
			case 32:
				dibColorTableSize = dibInfoHeader.mClrUsed;
				dibSkipColorTable = true;
				image = new RasterImage<ColorRGBA32>((uint)dibInfoHeader.mWidth, (uint)dibInfoHeader.mHeight);
				break;
			default:
				throw new Exception();
			}
			if (dibColorTableSize > 0) {
				if (dibSkipColorTable == false) {
					fs.Seek(DibFileHeader.Size+dibInfoHeader.mHeaderSize, SeekOrigin.Begin);
					dibColorTable = new DibBGRA[dibColorTableSize];
					for (int i = 0; i < dibColorTableSize; i++)
						dibColorTable[i] = new DibBGRA(br);
					fs.Seek(dibFileHeader.mBitsOffset, SeekOrigin.Current);
				} else
					fs.Seek((dibColorTableSize*DibBGRA.Size)+dibFileHeader.mBitsOffset, SeekOrigin.Current);
			} else
				fs.Seek(dibFileHeader.mBitsOffset, SeekOrigin.Begin);
			
			#endregion

			#region Read Pixels

			if (dibTopDown == false) {
				switch ((DibCompression)dibInfoHeader.mCompression) {
				case DibCompression.None:
					#region Read Uncompressed DIB

					// Determine line width (4 byte aligned)
					dibLineWidth = (((dibInfoHeader.mWidth * dibInfoHeader.mBitCount) / 8) + 3) & ~3;
					dibLine = new byte[dibLineWidth];
					
					for (uint h = 0; h < dibInfoHeader.mHeight; h++) {
						// Read dibIndex line (from bottom)
						br.Read(dibLine, 0, dibLineWidth);
						
						switch (dibInfoHeader.mBitCount) {
						case 1:
						case 4:
						case 8:
							Debug.Assert(dibColorTable != null);
							break;
						case 16:
							for (uint w = 0; h < dibInfoHeader.mWidth; w++) {
								uint dibLineIdx = w*2;
								
								//dibLayer.mLayerData[dibIndex].Blue = dibLine[dibLineIdx+0];
								//dibLayer.mLayerData[dibIndex].Green = dibLine[dibLineIdx+1];
								//dibLayer.mLayerData[dibIndex].Red = dibLine[dibLineIdx+2];
							}
							break;
						case 24:
							for (uint w = 0; w < dibInfoHeader.mWidth; w++) {
								ColorRGB24 c = new ColorRGB24();
								uint dibLineIdx = w*3;
								
								// Get pixel data
								c.Blue = dibLine[dibLineIdx+0];
								c.Green = dibLine[dibLineIdx+1];
								c.Red = dibLine[dibLineIdx+2];
								// Store image pixel
								image[w,h] = c;
							}
							break;
						case 32:
							for (uint w = 0; h < dibInfoHeader.mWidth; w++) {
								uint dibLineIdx = w*4;
								
								//dibLayer.mLayerData[dibIndex].Blue = dibLine[dibLineIdx+0];
								//dibLayer.mLayerData[dibIndex].Green = dibLine[dibLineIdx+1];
								//dibLayer.mLayerData[dibIndex].Red = dibLine[dibLineIdx+2];
								//dibLayer.mLayerData[dibIndex].Alpha = dibLine[dibLineIdx+3];
							}
							break;
						}
					}

					#endregion
					break;
				case DibCompression.RLE8:
					#region Read RLE8 compressed DIB
					
					#endregion
					break;
				case DibCompression.RLE4:
					#region Read RLE4 compressed DIB
					
					#endregion
					break;
				case DibCompression.Bitfields:
					#region Read BITFIELD compressed DIB
					
					#endregion
					break;
				default:
					throw new Exception();
				}
			} else {
				#region Read Top-Down uncompressed DIB
				
				#endregion
			}

			#endregion

			return (image);
		}

		/// <summary>
		/// Save image pixels to file.
		/// </summary>
		/// <param name="fs">
		/// A <see cref="System.IO.FileStream"/> which stores the image data.
		/// </param>
		/// <param name="image">
		/// A <see cref="Image"/> holding the data to be stored.
		/// </param>
		/// <param name="criteria">
		/// A <see cref="ImageCodec.CodecCriteria"/> specifying parameters for loading an image stream.
		/// </param>
		public override void Save(Stream fs, Image image, ImageFormat format, ImageCodec.CodecCriteria criteria)
		{
			BinaryWriter bw = new BinaryWriter(fs);
			DibFileHeader dibFileHeader = new DibFileHeader(image);
			DibInfoHeader dibInfoHeader = new DibInfoHeader(image);
			int dibLineWidth;
			byte[] dibLine;

			// Write file header
			dibFileHeader.Write(bw);
			// WRite info header
			dibInfoHeader.Write(bw);

			switch ((DibCompression)dibInfoHeader.mCompression) {
			case DibCompression.None:
				#region Write Uncompressed DIB

				// Determine line width (4 byte aligned)
				dibLineWidth = (((dibInfoHeader.mWidth * dibInfoHeader.mBitCount) / 8) + 3) & ~3;
				dibLine = new byte[dibLineWidth];

				for (uint h = 0; h < dibInfoHeader.mHeight; h++) {
					switch (dibInfoHeader.mBitCount) {
					case 24:
						for (uint w = 0; w < dibInfoHeader.mWidth; w++) {
							ColorRGB24 c = new ColorRGB24();
							uint dibLineIdx = w*3;
							
							c[0] = image[w,h][0];
							c[1] = image[w,h][1];
							c[2] = image[w,h][2];

							// Set pixel data
							dibLine[dibLineIdx+0] = c.Red;
							dibLine[dibLineIdx+1] = c.Blue;
							dibLine[dibLineIdx+2] = c.Green;
						}

						// Write DIB line
						bw.Write(dibLine);
						break;
					}
				}

				#endregion
				break;
			}

			bw.Close();
		}
		
		#endregion
	}
}
